//
//  Network.m
//  YanXiuWang
//
//  Created by 慧趣小歪 on 16/11/16.
//  Copyright © 2016年 小分队. All rights reserved.
//

#import "Network.h"
#import <objc/runtime.h>
#import "NSString+Format.h"

@interface RequestNormal ()

@property (nonatomic, strong) NSString *URL;
@property (nonatomic, strong) NSString *GETParams;
@property (nonatomic, strong) NSData *POSTParams;
@property (nonatomic, strong) NSDictionary *Headers;

@end

@interface NetworkManager ()

@property (nonatomic, strong) NSOperationQueue *queue;

+ (NSMutableURLRequest *)getURLRequestWithTask:(RequestNormal *)task;
+ (NSString *)getParamsFromDictionary:(NSDictionary *)dict;

@end

#pragma mark - NSURLSessionDelegate

@interface NetworkManager (SessionDelegate) <NSURLSessionDelegate>

@end

@interface NetworkQueue ()

- (void)resumeNext;

@end

@implementation NetworkQueue

- (Reachability *)hostReachability {
    static Reachability *_hostReachability;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _hostReachability = [Reachability reachabilityWithHostName:@"www.apple.com"];
    });
    return _hostReachability;
}


- (NSURLSession *)defaultSession {
    static NSURLSession *session;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
        session = [NSURLSession sessionWithConfiguration:config
                                                delegate:nil
                                           delegateQueue:[NSOperationQueue new]];
    });
    
    return session;
}

- (BOOL)completeURLSessionTask:(NSURLSessionTask * _Nonnull)task {
    if ([task isEqual:_currentTask]) {
        _currentTask = nil;
        return YES;
    }
    return NO;
}

- (void)resumeNext {
    static dispatch_queue_t queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    });
    
    // 在同一个后台线程下判断 是否有正在执行的网络请求已保障线程安全性;
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue, ^{
        // 如果有正在执行的网络请求则忽略
        if (weakSelf.currentTask != nil) return;
        
        // 挑选队列中优先级最高的请求执行
        for (NSURLSessionTask *task in weakSelf.requests) {
            if (weakSelf.currentTask == nil || weakSelf.currentTask.priority < task.priority) {
                weakSelf.currentTask = task;
            }
        }
        
        // 如果没有可执行的网络请求则忽略
        if (weakSelf.currentTask == nil) return;
        
        // 从队列中移除此请求 防止重复
        [weakSelf.requests removeObject:weakSelf.currentTask];
        // 开始执行 此网络请求
        [weakSelf.currentTask resume];
    });
    
}

- (nonnull NET_URL)request {
    
    return ^(NSString *url) {
        RequestNormal *task = [RequestNormal new];
        task.URL = url;
        task.queue = self;
        return task;
    };
}

@end


#pragma mark - http 响应结果
@implementation ResponseNormal

- (instancetype)initWithSource:(NSDictionary *)source {
    self = [super init];
    if (self) {
        _source = [source copy];
    }
    return self;
}

- (NSString *)params {
    return _source[@"params"];
}

- (NSURLRequest *)request {
    return _source[@"request"];
}

- (NSURLRequest *)urlRequest {
    return _source[@"urlRequest"];
}

- (int)stateCode {
    return [_source[@"state"] intValue];
}

- (NSDate *)serverDate {
    return _source[@"date"];
}

- (NSError *)error {
    return _source[@"error"];
}

- (NSURLSessionTask *)task {
    return _source[@"task"];
}

- (NSData *)data {
    return _source[@"data"];
}

@end

@implementation RequestNormal

- (instancetype)init {
    self = [super init];
    if (self) {
        _blockMap = [NSMutableDictionary dictionary];
    }
    return self;
}

- (nonnull NET_GET)get {
    __weak typeof(self) weakSelf = self;
    return ^(NSDictionary *params) {
        weakSelf.GETParams = [NetworkManager getParamsFromDictionary:params];
        return weakSelf;
    };
}

- (NET_POST_ARRAY)postArray {
    return ^(NSArray<__kindof NSDictionary *> * _Nonnull array) {
        NSMutableArray *params = [NSMutableArray arrayWithCapacity:array.count];
        
        for (NSDictionary *dict in array) {
            [params addObject:[NetworkManager getParamsFromDictionary:dict]];
        }
        NSString *json = [params componentsJoinedByString:@"&"];
        NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
        self.POSTParams = data;
        return self;
    };
}

- (nonnull NET_POST)post {
//    __weak typeof(self) weakSelf = self;
    return ^(NSDictionary *params) {
        NSString *json = [NetworkManager getParamsFromDictionary:params];
        NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
        self.POSTParams = data;
        return self;
    };
}

- (nonnull NET_POST_JSON)postJSON {
    __weak typeof(self) weakSelf = self;
    return ^(NSString *json) {
        NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:
                                   @"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#._-"];
        json = [json stringByAddingPercentEncodingWithAllowedCharacters:charSet];
        NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
        weakSelf.POSTParams = data;
        return weakSelf;
    };
}

- (nonnull NET_POST_DATA)postData {
    __weak typeof(self) weakSelf = self;
    return ^(NSData *data) {
        weakSelf.POSTParams = data;
        return weakSelf;
    };
}

- (nonnull NET_HeaderField)header {
    __weak typeof(self) weakSelf = self;
    return ^(NSString * header, id value) {
        NSMutableDictionary *dict = [weakSelf.Headers mutableCopy] ?: [NSMutableDictionary dictionary];
        [dict setObject:value forKey:header];
        weakSelf.Headers = [dict copy];
        return weakSelf;
    };
}

- (nonnull NSURLSessionTask*)commit:(OnComplete _Nonnull)completeBlock {
    
    __block OnComplete onComplete = [completeBlock copy];
    __block NSMutableDictionary *source = [NSMutableDictionary dictionary];
    __block NetworkQueue *queue = self.queue;

    NSURLRequest *request = [[NetworkManager getURLRequestWithTask:self] copy];
    NSURLSession *session = [NSURLSession sharedSession];
    session.configuration.timeoutIntervalForRequest = 30;
    
    source[@"params"] = [[NSString alloc] initWithData:self.POSTParams encoding:NSUTF8StringEncoding];
    source[@"request"] = self;
    source[@"urlRequest"] = request;
    // 通知栏显示网络状态
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    
    __block NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        // 在队列中完成此次网络请求, 使队列可以等待下一次队列请求开始
        [queue completeURLSessionTask:task];
        
        // 通知栏关闭网络状态
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        
        source[@"task"] = task;
        source[@"data"] = data;
        source[@"error"] = error;
        
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
            source[@"state"] = @(httpResponse.statusCode);
            
            NSString *dateString = httpResponse.allHeaderFields[@"Date"] ?: @"Thu, 01 Jan 1970 00:00:00 GMT";
            NSDateFormatter *formatter = [NSDateFormatter new];
            formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
            formatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z";
            
            NSDate *date = [formatter dateFromString:dateString] ?: [NSDate dateWithTimeIntervalSince1970:0];
            [NetworkManager shared].timeOffset = date.timeIntervalSince1970 - [NSDate date].timeIntervalSince1970;
            source[@"date"] = date;
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            onComplete([[ResponseNormal alloc] initWithSource:source]);
        });
        
        // 请求队列开始执行下一个请求
        [queue resumeNext];
    }];
    
    // 请求队列添加请求任务, 并开始执行
    if (queue != nil) {
        [queue.requests addObject:task];
        [queue resumeNext];
    } else {
        // 如果没有请求队列 则直接执行
        [task resume];
    }
    
    return task;
}
- (void)dealloc {
    
    print(@"  --==>  request释放%@", self.URL);
}
@end

@implementation NetworkManager

- (NSDate *)serverDate {
    return [NSDate dateWithTimeIntervalSince1970:[NSDate date].timeIntervalSince1970 + _timeOffset];
}

// 默认网络请求响应队列
- (NetworkQueue *)defaultQueue {
    static NetworkQueue *queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = [NetworkQueue new];
        queue.requests = [NSMutableArray arrayWithCapacity:1];
    });
    return queue;
}

- (nonnull NET_URL)request {
    return self.defaultQueue.request;
}

+ (NSMutableURLRequest *)getURLRequestWithTask:(RequestNormal *)task {
    
//    NSString *params = @"";
    
    NSURL *url;
    // 如果有 Get 参数 则将他们拼到 URL 后面
    if (task.GETParams != nil && task.GETParams.length > 0) {
        url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", task.URL, task.GETParams]];
    } else {
        url = [NSURL URLWithString:task.URL];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:150.0];
    
    // 如果有 Post 参数 则使用 POST 模式发送请求
    if (task.POSTParams != nil && task.POSTParams.length > 0) {
        request.HTTPMethod = @"POST";
        request.HTTPBody = task.POSTParams;
        [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)task.POSTParams.length]
       forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        
    } else {
        request.HTTPMethod = @"GET";
    }
    
    // 如果有 HeaderField 则设置
    for (NSString *key in task.Headers.allKeys) {
        id value = task.Headers[key];
        [request setValue:[NSString stringWithFormat:@"%@", value] forHTTPHeaderField:key];
    }
    return request;
}

+ (NSString *)getParamsFromDictionary:(NSDictionary *)dict {
    if (dict == nil || dict.count == 0) {
        return nil;
    }
    NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:
                               @"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#._-="];
    NSMutableString *params = [NSMutableString string];
    for (NSString *key in dict.allKeys) {
        id value = dict[key];
        NSString *k = [key stringByAddingPercentEncodingWithAllowedCharacters:charSet];
        if (params.length > 0) {
            [params appendString:@"&"];
        }
        if ([value isKindOfClass:[NSNull class]]) {
            [params appendString:k];
        } else {
            NSString *v = [NSString stringWithFormat:@"%@", value];
            v = [v stringByAddingPercentEncodingWithAllowedCharacters:charSet];
            [params appendFormat:@"%@=%@", k, v];
        }
    }
    return [params copy];
}

@end

@implementation NSURLSessionTask (Response)

- (__kindof ResponseNormal *)netResponse {
    return objc_getAssociatedObject(self, @selector(netResponse));
}

- (void)setNetResponse:(__kindof ResponseNormal *)netResponse {
    objc_setAssociatedObject(self, @selector(netResponse), netResponse, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

@implementation NSProgress (Percent)

- (double)progressPercent {
    if (self.totalUnitCount == 0) return 0;
    return (double)self.completedUnitCount / (double)self.totalUnitCount;
}

@end

#pragma mark - 单例相关

static dispatch_once_t onceToken;
static NetworkManager *instance;
@implementation NetworkManager (Once)

+ (nonnull instancetype)shared {
    return [NetworkManager new];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

- (instancetype)init {
    dispatch_once(&onceToken, ^{
        instance = [super init];
    });
    self = instance;
    return self;
}

- (id)copy {
    return self;
}

- (id)mutableCopy {
    return self;
}

@end
